/*
 *  Copyright (C) 2010 Zubanov Dmitry
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 *
 * @author Zubanov Dmitry aka javadimon, zubanov@gmail.com
 * Omsk, Russia, created 29.05.2010
 * (C) Copyright by Zubanov Dmitry
 */
package org.jssdb.core.ram;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jssdb.core.DBProperties;
import org.jssdb.core.filesystem.ModifyLogger;
import org.jssdb.core.event.EventSource;
import org.jssdb.core.filesystem.DirWatcher;
import org.jssdb.core.filesystem.FilesListInfo;
import org.jssdb.core.handler.FileStorageHandler;
import org.jssdb.core.handler.Handler;
import org.jssdb.core.net.MulticastCommand;
import org.jssdb.core.net.MulticastServer;
import org.jssdb.core.net.ReplicationServer;

public class RamHandler extends ArrayList<Map<File, ArrayList<Map<Long, Object>>>>
        implements Observer {

    private static RamHandler self = null;
    private Handler handler;
    private static final Logger logger = Logger.getLogger(RamHandler.class.getName());
    private boolean isInit = false, isWatch = false;
    private EventSource eventSource = EventSource.getInstance();

    private RamHandler() {
        bindObserver();
        handler = Handler.getInstance();
    }

    public static RamHandler getInstance() {
        if (self == null) {
            self = new RamHandler();
        }

        return self;
    }

    private void bindObserver() {
        eventSource.addObserver(this);
    }

    public void setIdOffset(int autoStart, int autoIncrement, int autoOffset) {
        FileStorageHandler.getDefault().setIdOffset(autoStart, autoIncrement, autoOffset);
    }

    public String[] getObjectItemList() {
        String str[] = new String[this.size()];
        int i = 0;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            str[i] = ((File) m.keySet().toArray()[0]).getName();
            i++;
        }

        return str;
    }

    public void forceInitDB() {
        if (isInit) {
            isInit = false;
        }

        initDB();
    }

    public void initDB() {
        String dbDir = DBProperties.getInstance().getPathToDB();
        ArrayList<TimerTask> listTimerTask = new ArrayList();
        if (isInit) {
            return;
        }

        super.clear();
        File dir = new File(dbDir);

        File[] dirs = dir.listFiles();
        for (int i = 0; i < dirs.length; i++) {
            File file = dirs[i];
            if (file.isDirectory()) {
                logger.info(file.getName());
                eventSource.fireCustomEvent(file.getName());

                try {
                    ArrayList<Map<Long, Object>> aList = handler.getCollection(file.getName());
                    Map<File, ArrayList<Map<Long, Object>>> map = new HashMap<File, ArrayList<Map<Long, Object>>>();
                    map.put(file, aList);
                    super.add(map);

                    if (isWatch) {
                        TimerTask task = new DirWatcher(file.getCanonicalPath()) {

                            @Override
                            protected void onChange(File file, int action) {
                                logger.log(Level.INFO, "File {0} action: {1}", new Object[]{file, action});
                                if (ReplicationServer.getInstance().isEnable()) {
                                    ModifyLogger.getInstance().addModify(file, action);
                                    ReplicationServer.getInstance().sendToAllModifyAction(file, action);
                                }
                            }
                        };

                        listTimerTask.add(task);
                    }

                    FilesListInfo.getInstance().addDir(file);

                } catch (Exception e) {
                    logger.log(Level.SEVERE, null, e);
                }
            }
        }

        // replication with other network databases
        if (ReplicationServer.getInstance().isEnable()) {
            try {
                MulticastServer.getInstance().sendRequest(new MulticastCommand(MulticastCommand.GET_INFO));
            } catch (Exception e) {
                logger.log(Level.SEVERE, dbDir, e);
            }
        }

        if (isWatch) {
            DirWatcherTimerStart(listTimerTask);
        }

        isInit = true;
    }

    private void DirWatcherTimerStart(ArrayList<TimerTask> listTimerTask) {
        for (TimerTask timerTask : listTimerTask) {
            Timer timer = new Timer();
            timer.schedule(timerTask, new Date(), 10000);
        }
    }

    public void setWatcherStatus(boolean isWatch) {
        this.isWatch = isWatch;
    }

    /**
     * 
     * @param String className as full class name
     * @return ArrayList collection or null
     */
    public ArrayList<Map<Long, Object>> getCollection(String className, boolean isClone) {
        ArrayList<Map<Long, Object>> aList = null;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if ((((File) m.keySet().toArray()[0])).getName().equals(className)) {
                if(isClone){
                    aList = (ArrayList<Map<Long, Object>>) m.get((File) m.keySet().toArray()[0]).clone();
                } else {
                    aList = (ArrayList<Map<Long, Object>>) m.get((File) m.keySet().toArray()[0]);
                }
                
            }
        }

        return aList;
    }

    /**
     * 
     * @param Class cls
     * @return ArrayList collection or null
     */
    public ArrayList<Map<Long, Object>> getCollection(Class cls, boolean isClone) {
        ArrayList<Map<Long, Object>> aList = null;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if ((((File) m.keySet().toArray()[0])).getName().equals(cls.getName())) {
                if(isClone){
                    aList = (ArrayList<Map<Long, Object>>) m.get((File) m.keySet().toArray()[0]).clone();
                } else {
                    aList = (ArrayList<Map<Long, Object>>) m.get((File) m.keySet().toArray()[0]);
                }
            }
        }

        return aList;
    }

    public boolean updateObject(long id, Object object) {
        boolean result = false;
        result = handler.update(id, object);
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            String key = ((File) m.keySet().toArray()[0]).getName();
            if (key.equals(object.getClass().getName())) {
                ArrayList<Map<Long, Object>> aList = m.get((File) m.keySet().toArray()[0]);
                for (Map<Long, Object> ma : aList) {
                    if ((Long) ma.keySet().toArray()[0] == id) {
                        ma.put(id, object);
                    }
                }
            }
        }

        return result;
    }

    public boolean deleteObject(long id, String className) {
        boolean result = handler.delete(id, className);
        if (!result) {
            return result;
        }

        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if (className.equals(((File) m.keySet().toArray()[0]).getName())) {
                ArrayList<Map<Long, Object>> a = m.get((File) m.keySet().toArray()[0]);
                for (Map<Long, Object> mr : a) {
                    if ((Long) mr.keySet().toArray()[0] == id) {
                        a.remove(mr);
                        break;
                    }
                }
            }
        }

        return result;
    }

    public boolean deleteObject(long id, Class cls) {
        if (cls == null) {
            return false;
        }
        return deleteObject(id, cls.getName());
    }

    public long addObject(Object obj) {
        if(obj == null){
            logger.log(Level.INFO, "added object is null");
            return -1;
        }

        long id = handler.add(obj);
        if (isPresent(obj)) {
            // add to ram
            ArrayList<Map<Long, Object>> aList = getCollection(obj.getClass(), false);
            Map<Long, Object> m = new HashMap<Long, Object>();
            m.put(id, obj);
            aList.add(m);

        } else {
            // load collection to ram
            Class cls = obj.getClass();
            try {
                ArrayList<Map<Long, Object>> aList = handler.getCollection(handler.getMinId(cls),
                        handler.getMaxId(cls),
                        cls);
                Map<File, ArrayList<Map<Long, Object>>> map = new HashMap<File, ArrayList<Map<Long, Object>>>();
                File f = new File(DBProperties.getInstance().getPathToDB() + obj.getClass().getName());
                map.put(f, aList);
                super.add(map);
            } catch (Exception e) {
                logger.log(Level.SEVERE, null, e);
            }
        }

        return id;
    }

    public long addObject(long id, Object obj) {
        if(obj == null){
            return -1;
        }

        handler.add(id, obj);
        if (isPresent(obj)) {
            // add to ram
            ArrayList<Map<Long, Object>> aList = getCollection(obj.getClass(), false);
            Map<Long, Object> m = new HashMap<Long, Object>();
            m.put(id, obj);
            aList.add(m);

        } else {
            // load collection to ram
            Class cls = obj.getClass();
            try {
                ArrayList<Map<Long, Object>> aList = handler.getCollection(handler.getMinId(cls),
                        handler.getMaxId(cls),
                        cls);
                Map<File, ArrayList<Map<Long, Object>>> map = new HashMap<File, ArrayList<Map<Long, Object>>>();
                File f = new File(DBProperties.getInstance().getPathToDB() + obj.getClass().getName());
                map.put(f, aList);
                super.add(map);
            } catch (Exception e) {
                logger.log(Level.SEVERE, null, e);
            }
        }

        return id;
    }

    public long addObject(long id, long lastModified, Object obj) {
        if(obj == null){
            return -1;
        }

        handler.add(id, lastModified, obj);

        if (isPresent(obj)) {
            // add to ram
            ArrayList<Map<Long, Object>> aList = getCollection(obj.getClass(), false);
            Map<Long, Object> m = new HashMap<Long, Object>();
            m.put(id, obj);
            aList.add(m);

        } else {
            // load collection to ram
            Class cls = obj.getClass();
            try {
                ArrayList<Map<Long, Object>> aList = handler.getCollection(handler.getMinId(cls),
                        handler.getMaxId(cls),
                        cls);
                Map<File, ArrayList<Map<Long, Object>>> map = new HashMap<File, ArrayList<Map<Long, Object>>>();
                File f = new File(DBProperties.getInstance().getPathToDB() + obj.getClass().getName());
                map.put(f, aList);
                super.add(map);
            } catch (Exception e) {
                logger.log(Level.SEVERE, null, e);
            }
        }

        return id;
    }

    public int getMaxId(String className) {
        int val = -1;

        Class cls = null;
        try {
            cls = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(RamHandler.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (cls != null) {
            val = getMaxId(cls);
        }

        return val;
    }

    public int getMinId(String className) {
        int val = -1;

        Class cls = null;
        try {
            cls = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(RamHandler.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (cls != null) {
            val = getMinId(cls);
        }

        return val;
    }

    public int getMaxId(Class cls) {
        int max = 0;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if (((File) m.keySet().toArray()[0]).getName().equals(cls.getName())) {
                for (Map<Long, Object> ma : m.get((File) m.keySet().toArray()[0])) {
                    if ((Integer) ma.keySet().toArray()[0] > max) {
                        max = (Integer) ma.keySet().toArray()[0];
                    }
                }
            }
        }

        return max;
    }

    public int getMinId(Class cls) {
        int min = Integer.MAX_VALUE;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if (((File) m.keySet().toArray()[0]).getName().equals(cls.getName())) {
                for (Map<Long, Object> ma : m.get((File) m.keySet().toArray()[0])) {
                    if ((Integer) ma.keySet().toArray()[0] < min) {
                        min = (Integer) ma.keySet().toArray()[0];
                    }
                }
            }
        }

        return min;
    }

    public Object getObjectById(long id, Class cls) {
        Object obj = null;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if (((File) m.keySet().toArray()[0]).getName().equals(cls.getName())) {
                ArrayList<Map<Long, Object>> a = m.get((File) m.keySet().toArray()[0]);
                for (Map<Long, Object> ma : a) {
                    if (id == (Long) ma.keySet().toArray()[0]) {
                        obj = ma.get(id);
                    }
                }
            }
        }

        return obj;
    }

    public Object getObjectById(long id, String className) {
        Object obj = null;
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            boolean isFind = false;
            if (((File) m.keySet().toArray()[0]).getName().equals(className)) {
                ArrayList<Map<Long, Object>> a = m.get((File) m.keySet().toArray()[0]);
                for (Map<Long, Object> ma : a) {
                    if (id == (Long) ma.keySet().toArray()[0]) {
                        obj = ma.get(id);
                        isFind = true;
                        break;
                    }
                }
            }

            if (isFind) {
                break;
            }
        }

        return obj;
    }

    private boolean isPresent(Object obj) {
        boolean result = false;
        if(obj == null){
            return result;
        }
        
        for (Map<File, ArrayList<Map<Long, Object>>> m : this) {
            if (((File) m.keySet().toArray()[0]).getName().equals(obj.getClass().getName())) {
                result = true;
            }
        }
        return result;
    }

    @Override
    public void update(Observable o, Object arg) {
        //throw new UnsupportedOperationException("Not supported yet.");
    }
}
